iT邦幫忙

2024 iThome 鐵人賽

DAY 27
0
Modern Web

從零開始:全端新手的困境與成長系列 第 27

Day27 密碼驗證流程 – MySQL 中的密碼儲存與檢查

  • 分享至 

  • xImage
  •  

有沒有發現我們之前的登入流程少了一個超重要的步驟?沒錯,就是密碼驗證!之前的範例中,我們根本沒檢查用戶的密碼,這樣根本不是一個完整的登入流程嘛~今天我們就來補上這個環節,教你如何在 MySQL 中安全儲存密碼,並在用戶登入時進行正確的密碼驗證,讓整個登入系統更完整、更安全!

https://ithelp.ithome.com.tw/upload/images/20241005/201683266YjZZMRn8j.png

大綱:

  1. 密碼的安全儲存:為什麼不能直接儲存用戶密碼?
  2. 使用 bcrypt 來加密密碼
  3. 在 MySQL 中儲存加密後的密碼
  4. 在登入流程中驗證密碼
  5. 整合密碼驗證與 JWT 生成
  6. 完整的登入流程範例
  7. 前後端整合:實作密碼驗證與 JWT 登入流程
  8. 密碼驗證這麼重要

1. 密碼的安全儲存:為什麼不能直接儲存用戶密碼?

千萬記住一件事:千萬不要直接儲存用戶的明文密碼!如果你的資料庫被入侵了,用戶的密碼就會立刻暴露,這樣就 GG 了,不僅損害用戶隱私,還可能引發更大的安全問題。為了避免這種情況,我們會對密碼進行加密後再儲存,並且在用戶登入時進行安全的比對。


2. 使用 bcrypt 來加密密碼

要安全儲存密碼,我們可以使用 bcrypt 這個工具來加密。它有著很強的加密和鹽值功能,能夠防止暴力破解。這個工具可是每個開發者的好夥伴!

安裝 bcrypt:

npm install bcrypt

3. 在 MySQL 中儲存加密後的密碼

當用戶註冊時,我們會先將用戶的密碼進行加密,再把加密後的密碼儲存到 MySQL 中。舉個例子,假設我們有一個 Users 表格,裡面有 emailpassword 這兩個欄位。

註冊 API 範例:

// controllers/authController.js
const bcrypt = require('bcrypt');
const db = require('../models'); // 引入你的 MySQL models

// 註冊用戶並加密密碼
exports.register = async (req, res) => {
  const { email, password } = req.body;

  try {
    // 將密碼加密
    const hashedPassword = await bcrypt.hash(password, 10); // 10 是 salt rounds 值,越高越安全,但越慢
    // 儲存加密後的密碼到資料庫
    await db.User.create({ email, password: hashedPassword });

    res.json({ message: '註冊成功!' });
  } catch (error) {
    res.status(500).json({ message: '註冊失敗', error });
  }
};

在這段程式中,我們將用戶密碼加密後儲存到 MySQL。這樣,即便資料庫被入侵,駭客也無法直接讀取到用戶的明文密碼,讓安全性大大提高!


4. 在登入流程中驗證密碼

接下來就是重點啦!當用戶登入時,我們需要將用戶輸入的密碼和資料庫裡儲存的加密密碼進行比較,這時候就可以用 bcrypt.compare 來幫忙。

登入 API 範例:

// controllers/authController.js
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');

// 登入並驗證密碼
exports.login = async (req, res) => {
  const { email, password } = req.body;

  try {
    // 查找用戶
    const user = await db.User.findOne({ where: { email } });
    if (!user) {
      return res.status(404).json({ message: '用戶不存在' });
    }

    // 驗證密碼
    const isMatch = await bcrypt.compare(password, user.password);
    if (!isMatch) {
      return res.status(401).json({ message: '密碼錯誤' });
    }

    // 密碼正確,生成 JWT
    const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' });

    res.json({ token });
  } catch (error) {
    res.status(500).json({ message: '登入失敗', error });
  }
};

這段程式會在登入時先檢查用戶是否存在,再用 bcrypt 比對密碼。如果密碼正確,接下來就生成 JWT,回傳給前端。這樣,前端就可以攜帶 JWT 進行身份驗證啦!


5. 整合密碼驗證與 JWT 生成

登入流程總結起來就是這幾步:

  1. 查找用戶:根據使用者名稱從資料庫查找用戶。
  2. 驗證密碼:用 bcrypt.compare 將使用者輸入的密碼與資料庫中的加密密碼進行比對。
  3. 生成 JWT:如果密碼比對成功,生成 JWT,並將其回傳給前端。

這樣,我們就完成了密碼驗證與 JWT 生成的整合,整個流程既安全又有效率。


6. 完整的登入流程範例

以下是註冊與登入的完整範例:

// controllers/authController.js
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const db = require('../models'); // 假設我們的資料庫 models

// 用戶註冊
exports.register = async (req, res) => {
  const { email, password } = req.body;

  try {
    const hashedPassword = await bcrypt.hash(password, 10);
    await db.User.create({ email, password: hashedPassword });
    res.json({ message: '註冊成功!' });
  } catch (error) {
    res.status(500).json({ message: '註冊失敗', error });
  }
};

// 用戶登入
exports.login = async (req, res) => {
  const { email, password } = req.body;

  try {
    const user = await db.User.findOne({ where: { email } });
    if (!user) {
      return res.status(404).json({ message: '用戶不存在' });
    }

    const isMatch = await bcrypt.compare(password, user.password);
    if (!isMatch) {
      return res.status(401).json({ message: '密碼錯誤' });
    }

    const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' });
    res.json({ token });
  } catch (error) {
    res.status(500).json({ message: '登入失敗', error });
  }
};

7. 前後端整合:實作密碼驗證與 JWT 登入流程

現在我們把這個流程整合到前後端,實現完整的登入流程。

前端處理流程:

  1. 用戶輸入 用戶名密碼,發送登入請求給後端。
  2. 後端比對密碼,通過後生成 JWT,並回傳給前端。
  3. 前端接收 JWT,並將其儲存在 Local StorageState 中,方便後續 API 請求使用。

完整流程示範:

前端 login.component.ts

import { AuthService } from './auth.service';
import { Router } from '@angular/router';

export class LoginComponent {
  email: string = '';
  password: string = '';

  constructor(private authService: AuthService, private router: Router) {}

  login() {
    this.authService.login(this.email, this.password).subscribe(
      (response) => {
        localStorage.setItem('token', response.token); // 儲存 JWT
        this.router.navigate(['/home']); // 登入成功後跳轉至首頁
      },
      (error) => {
        console.error('登入失敗', error);
      }
    );
  }
}

這樣一來,我們的前後端登入流程就完整實現啦!當用戶登入後,JWT 就會保存在 Local Storage 中,並且會自動附帶到每次 API 請求的標頭裡,確保每個請求都會攜帶正確的身份驗證資訊。


8. 密碼驗證這麼重要

今天我們總算補齊了登入流程中最關鍵的部分——密碼驗證。從如何加密儲存密碼,到登入時比對正確的密碼,整個流程都得到完整的處理。而且還將 JWT 驗證結合起來,實現了我們完整的身份驗證系統。現在你的用戶資料安全大大提升,密碼再也不是薄弱環節!

這次我們經歷了:

  1. 密碼加密儲存:利用 bcrypt 加密密碼,確保用戶資料不會輕易被竊取。
  2. 登入時的密碼比對:確保只有密碼正確的用戶才能取得 JWT,進行後續操作。
  3. JWT 的生成與傳遞:登入成功後生成 JWT,前端保存,後續每次 API 請求自動攜帶 Token。
  4. 前後端的無縫整合:前端將表單資料送到後端驗證,後端回傳 Token,前端保存並帶著 Token 進行後續的 API 請求。

上一篇
Day26 登入功能初體驗,JWT 的身份驗證流程!(下)
下一篇
Day28 Role-Based Permissions 權限管理的完整實作
系列文
從零開始:全端新手的困境與成長30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言